import ctypes
import platform
import tkinter

import cv2
import fitz
import numpy as np

from app.utils.logger import get_logger
from app.utils.pymupdf_tools import page_to_img

logger = get_logger(__name__)


def get_screen_size():
    """获取屏幕尺寸"""
    os_name = platform.system()
    if os_name == "Windows":
        user32 = ctypes.windll.user32
        screensize = user32.GetSystemMetrics(0), user32.GetSystemMetrics(1)
    elif os_name == "Darwin":  # MacOS
        import AppKit
        main_monitor = AppKit.NSScreen.screens()[0]  # 获取主显示器信息
        screensize = (int(main_monitor.frame().size.width), int(main_monitor.frame().size.height))
    else:  # Linux可能是其他情况
        root = tkinter.Tk()
        screensize = (root.winfo_screenwidth(), root.winfo_screenheight())
        root.destroy()
    return screensize


def show_image_with_roi(source, zoom_factor=1, is_show=False, roi=None, color_mode="RGB", window_name="img"):
    """
    显示带有感兴趣区域(ROI)的图像

    Args:
        source: 图像源，可以是文件路径、numpy数组或其他支持的图像格式
        zoom_factor (int): 图像缩放因子，默认为1(不缩放)
        is_show (bool): 是否显示图像，默认为False
        roi (tuple): 感兴趣区域的坐标(x0, y0, x1, y1)，默认为None
        color_mode: 目标颜色模式 ("RGB" 或 "BGR")。
        window_name: 默认值 img

    Returns:
        None
    """
    if not is_show:
        return

    img_cv = _load_image(source, zoom_factor, color_mode)
    if img_cv is None:
        logger.error("无法从source获取有效的图像")
        return

    # 按ROI坐标画矩形框
    _draw_roi_rectangle(img_cv, roi)

    # 初始化拖动相关变量
    drag_start = None
    current_offset = [0, 0]
    original_offset = [0, 0]

    # 鼠标位置信息
    mouse_pos = [0, 0]
    pixel_color = (0, 0, 0)
    image_size = (img_cv.shape[1], img_cv.shape[0])  # (width, height)

    # 创建共享数据容器供回调函数使用
    shared_data = {
        'drag_start': drag_start,
        'current_offset': current_offset,
        'original_offset': original_offset,
        'mouse_pos': mouse_pos,
        'pixel_color': pixel_color,
        'image_size': image_size,
        'display_img': None
    }

    # 更新状态显示
    def update_status():
        _update_mouse_status(shared_data, window_name)

    # 鼠标回调函数
    def mouse_callback(event, x, y, flags, param):
        _handle_mouse_events(event, x, y, flags, shared_data, update_status)

    # 创建窗口并设置鼠标回调
    cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)

    # 根据图像和屏幕尺寸配置窗口
    _configure_window(window_name, img_cv.shape[0], img_cv.shape[1])

    cv2.setMouseCallback(window_name, mouse_callback)

    # 显示循环
    _run_display_loop(img_cv, shared_data, update_status, window_name)


def _calculate_display_size(img_h, img_w, screen_h, screen_w):
    """
    根据图像尺寸和屏幕尺寸计算合适的显示尺寸

    Args:
        img_h (int): 图像高度
        img_w (int): 图像宽度
        screen_h (int): 屏幕高度
        screen_w (int): 屏幕宽度

    Returns:
        tuple: (display_width, display_height) 显示尺寸
    """
    # 如果图像高度小于或等于屏幕高度，则完整显示
    if img_h <= screen_h:
        return img_w, img_h
    else:
        # 否则按屏幕高度等比缩放
        scale = screen_h / img_h
        display_width = int(img_w * scale)
        display_height = screen_h
        return display_width, display_height


def _configure_window(window_name, img_h, img_w):
    """
    根据图像尺寸配置显示窗口

    Args:
        window_name (str): 窗口名称
        img_h (int): 图像高度
        img_w (int): 图像宽度
    """
    screen_w, screen_h = get_screen_size()

    # 计算显示尺寸
    display_w, display_h = _calculate_display_size(img_h, img_w, screen_h, screen_w)
    # 设置窗口居中
    cv2.moveWindow(window_name, (screen_w - display_w) // 2, (screen_h - display_h) // 2)

    # 如果图像尺寸小于屏幕尺寸，则移动到左上角并最大化显示
    if img_w <= screen_w and img_h <= screen_h:
        # 设置窗口大小
        cv2.resizeWindow(window_name, int(display_w * 1.5), int(display_h * 1.5))
    else:
        # 设置窗口大小
        cv2.resizeWindow(window_name, display_w, display_h)


def _load_image(source, zoom_factor, color_mode):
    """
    加载图像

    Args:
        source: 图像源，可以是文件路径、numpy数组或其他支持的图像格式
        zoom_factor (int): 图像缩放因子
        color_mode: 目标颜色模式 ("RGB" 或 "BGR")。

    Returns:
        numpy数组表示的图像
    """
    if isinstance(source, fitz.Page):
        img_cv = page_to_img(source, zoom_factor, color_mode=color_mode)
    elif isinstance(source, str):
        img_cv = cv2.imread(source)
    elif isinstance(source, np.ndarray):
        img_cv = source
    else:
        img_cv = None

    return img_cv


def _draw_roi_rectangle(img_cv, roi):
    """
    在图像上绘制ROI矩形

    Args:
        img_cv: 图像数组
        roi (tuple): 感兴趣区域的坐标(x0, y0, x1, y1)
    """
    if roi is not None:
        x0, y0, x1, y1 = roi
        # 将坐标转换为整数类型
        x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
        # 在彩色图像上绘制绿色矩形
        if len(img_cv.shape) == 3:
            cv2.rectangle(img_cv, (x0, y0), (x1, y1), (0, 0, 255), 1)
        else:
            # 如果由于某种原因仍然是灰度图，则使用白色绘制
            cv2.rectangle(img_cv, (x0, y0), (x1, y1), 255, 1)


def _update_mouse_status(shared_data, window_name):
    """
    更新鼠标状态显示

    Args:
        shared_data (dict): 包含所有共享数据的字典
        window_name (str): 窗口名称
    """
    current_offset = shared_data['current_offset']
    image_size = shared_data['image_size']
    mouse_pos = shared_data['mouse_pos']
    pixel_color = shared_data['pixel_color']
    display_img = shared_data['display_img']

    if display_img is not None:
        status_text = (f"Offset: ({-current_offset[0]}, {-current_offset[1]}) | " +
                       f"View: ({max(0, -current_offset[0])}, {max(0, -current_offset[1])}) - " +
                       f"({min(image_size[0], -current_offset[0] + display_img.shape[1])}, " +
                       f"{min(image_size[1], -current_offset[1] + display_img.shape[0])}) | " +
                       f"Image Size: {image_size[0]}x{image_size[1]} | " +
                       # f"Mouse: ({mouse_pos[0]}, {mouse_pos[1]}) | " +
                       f"RGB: {pixel_color}")
        cv2.displayOverlay(window_name, status_text, 0)  # 持续显示


def _handle_mouse_events(event, x, y, flags, shared_data, update_status_func):
    """
    处理鼠标事件

    Args:
        event: 鼠标事件类型
        x (int): 鼠标x坐标
        y (int): 鼠标y坐标
        flags: 鼠标事件标志
        shared_data (dict): 包含所有共享数据的字典
        update_status_func (function): 更新状态显示的函数
    """
    # 更新鼠标位置
    shared_data['mouse_pos'] = [x, y]
    display_img = shared_data['display_img']

    # 获取鼠标位置的像素颜色
    if display_img is not None and 0 <= y < display_img.shape[0] and 0 <= x < display_img.shape[1]:
        bgr = display_img[y, x]
        shared_data['pixel_color'] = (int(bgr[2]), int(bgr[1]), int(bgr[0]))  # BGR转RGB

    if event == cv2.EVENT_LBUTTONDOWN:
        # 开始拖动
        shared_data['drag_start'] = (x, y)
        shared_data['original_offset'] = shared_data['current_offset'].copy()

    elif event == cv2.EVENT_MOUSEMOVE and shared_data['drag_start'] is not None:
        # 更新拖动偏移量
        dx = x - shared_data['drag_start'][0]
        dy = y - shared_data['drag_start'][1]
        shared_data['current_offset'][0] = shared_data['original_offset'][0] + dx
        shared_data['current_offset'][1] = shared_data['original_offset'][1] + dy
        update_status_func()  # 拖动时更新状态

    elif event == cv2.EVENT_LBUTTONUP:
        # 结束拖动
        shared_data['drag_start'] = None
        shared_data['original_offset'] = shared_data['current_offset'].copy()

    # 鼠标移动时更新状态
    if event == cv2.EVENT_MOUSEMOVE:
        update_status_func()


def _run_display_loop(img_cv, shared_data, update_status_func, window_name):
    """
    运行显示循环

    Args:
        img_cv: 原始图像
        shared_data (dict): 包含所有共享数据的字典
        update_status_func (function): 更新状态显示的函数
    """
    h, w = img_cv.shape[:2]

    while True:
        # 检查窗口是否仍然存在
        if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
            break

        # 创建显示图像
        display_img = img_cv.copy()
        shared_data['display_img'] = display_img

        # 应用拖动偏移
        current_offset = shared_data['current_offset']
        if current_offset != [0, 0]:
            # 创建一个新的显示图像
            temp_img = np.zeros_like(display_img)
            # 计算源图像在目标图像中的位置
            src_x_start = max(0, -current_offset[0])
            src_y_start = max(0, -current_offset[1])
            src_x_end = min(w, w - current_offset[0])
            src_y_end = min(h, h - current_offset[1])

            dst_x_start = max(0, current_offset[0])
            dst_y_start = max(0, current_offset[1])
            dst_x_end = min(w, w + current_offset[0])
            dst_y_end = min(h, h + current_offset[1])

            # 复制可见部分
            if (src_x_end > src_x_start and
                    src_y_end > src_y_start and
                    dst_x_end > dst_x_start and
                    dst_y_end > dst_y_start):
                temp_img[dst_y_start:dst_y_end, dst_x_start:dst_x_end] = \
                    display_img[src_y_start:src_y_end, src_x_start:src_x_end]
            display_img = temp_img
            shared_data['display_img'] = display_img

        # 更新状态显示
        update_status_func()

        # 显示图像
        cv2.imshow(window_name, display_img)

        # 等待1毫秒看是否有按键
        key = cv2.waitKey(1) & 0xFF
        if key != 255:  # 有按键按下
            break

    cv2.destroyAllWindows()


def crop_image_with_roi(source, roi, zoom_factor=1.0, output_path=None, expand_roi=0):
    """
    使用ROI坐标裁剪图像，支持多种输入源并可选择保存结果

    Args:
        source: 输入源，可以是文件路径、fitz.Page对象或numpy数组
        roi: ROI坐标 (x0, y0, x1, y1) 其中 (x0, y0) 是左上角坐标，(x1, y1) 是右下角坐标
        zoom_factor: 缩放因子，仅对fitz.Page有效
        output_path: 可选，输出文件路径，如果不提供则不保存
        expand_roi: 可选，外扩ROI区域的像素数，默认为0（不外扩）。
                   数值大于0时向外扩展，数值小于0时向内收缩

    Returns:
        裁剪后的图像

    Examples:
        >>> # 不外扩裁切（默认行为）
        >>> cropped = crop_image_with_roi(source, (10, 10, 100, 100))
        >>> # 外扩1个像素
        >>> cropped = crop_image_with_roi(source, (10, 10, 100, 100), expand_roi=1)
        >>> # 内缩2个像素
        >>> cropped = crop_image_with_roi(source, (10, 10, 100, 100), expand_roi=-2)
        >>> # 外扩并保存
        >>> cropped = crop_image_with_roi(source, (10, 10, 100, 100), expand_roi=1, output_path="cropped.jpg")
    """
    img_cv = page_to_img(source, zoom_factor)

    if img_cv is None:
        logger.error("无法从source获取有效的图像")
        return None

    x0, y0, x1, y1 = roi
    # 如果需要外扩ROI，则进行相应调整
    if expand_roi != 0:
        x0, y0 = x0 - expand_roi, y0 - expand_roi  # 左上角坐标减去外扩值
        x1, y1 = x1 + expand_roi, y1 + expand_roi  # 右下角坐标加上外扩值

    # 将坐标转换为整数
    x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
    # 确保坐标在图像范围内
    x0 = max(0, x0)
    y0 = max(0, y0)
    y1 = min(img_cv.shape[0], y1)
    x1 = min(img_cv.shape[1], x1)

    # 裁剪图像 [y0:y1, x0:x1]
    cropped_image = img_cv[y0:y1, x0:x1]

    # 如果提供了输出路径，则保存图像
    if output_path is not None:
        try:
            success = cv2.imwrite(output_path, cropped_image)
            if success:
                logger.info(f"裁剪后的图像已保存至: {output_path}")
            else:
                logger.info(f"保存图像失败: {output_path}")
        except Exception as e:
            logger.info(f"保存图像时发生异常: {e}")

    return cropped_image


def base64_to_cv2(b64str):
    import base64

    data = base64.b64decode(b64str.encode("utf8"))
    data = np.frombuffer(data, np.uint8)
    data = cv2.imdecode(data, cv2.IMREAD_COLOR)
    return data


def process_image_roi(image, roi):
    """
    处理图像的ROI区域，只保留ROI区域内容，其余区域填充白色

    Args:
        image (numpy.ndarray): 输入图像
        roi (tuple): 感兴趣区域坐标 (x0, y0, x1, y1)

    Returns:
        numpy.ndarray: 处理后的图像
    """
    if roi is None:
        return image

    height, width = image.shape[:2]
    x0, y0, x1, y1 = roi

    # 确保ROI坐标在图像范围内
    x0 = max(0, int(x0))
    y0 = max(0, int(y0))
    x1 = min(width, int(x1))
    y1 = min(height, int(y1))

    # 创建一个白色背景的图像
    if len(image.shape) == 3:
        roi_image = np.full_like(image, 255)
    else:
        roi_image = np.full_like(image, 255)

    # 将ROI区域的内容复制到白色背景上
    roi_image[y0:y1, x0:x1] = image[y0:y1, x0:x1]

    return roi_image


def calculate_rectangle_center(rect):
    """
    计算矩形的中心点坐标

    Args:
        rect (tuple): 矩形坐标 (x0, y0, x1, y1)，其中 (x0, y0) 是一个角点，
                     (x1, y1) 是对角的另一个角点

    Returns:
        tuple: 中心点坐标 (cx, cy)
    """
    x0, y0, x1, y1 = rect
    cx = int((x0 + x1) / 2)
    cy = int((y0 + y1) / 2)
    return (cx, cy)


def group_text_lines_by_proximity(ocr_results, vertical_threshold=10):
    """
    根据文本行的垂直距离对相近的文本行进行分组
    可用于处理跨行过高的问题

    Args:
        ocr_results: OCR识别结果，包含文本框坐标
        vertical_threshold: 垂直方向上的分组阈值

    Returns:
        list: 分组后的文本行列表
    """
    if not ocr_results:
        return []

    # 计算每个文本框的中心点
    text_boxes_with_centers = []
    for result in ocr_results:
        if result and len(result) > 0:
            for line in result:
                if line and len(line) > 0:
                    # 获取文本框坐标
                    box = line[0]  # 假设box是[[x0,y0],[x1,y0],[x1,y1],[x0,y1]]
                    # 计算包围盒
                    x_coords = [point[0] for point in box]
                    y_coords = [point[1] for point in box]
                    x0, x1 = min(x_coords), max(x_coords)
                    y0, y1 = min(y_coords), max(y_coords)

                    # 计算中心点
                    center = calculate_rectangle_center((x0, y0, x1, y1))
                    text_boxes_with_centers.append({
                        'text': line[1][0],
                        'box': box,
                        'center': center,
                        'top': y0,
                        'bottom': y1
                    })

    # 根据垂直位置对文本行进行排序
    text_boxes_with_centers.sort(key=lambda x: x['center'][1])

    # 按照垂直距离分组
    groups = []
    if text_boxes_with_centers:
        current_group = [text_boxes_with_centers[0]]

        for i in range(1, len(text_boxes_with_centers)):
            prev_box = text_boxes_with_centers[i - 1]
            curr_box = text_boxes_with_centers[i]

            # 计算垂直方向上的距离
            distance = abs(curr_box['center'][1] - prev_box['center'][1])

            # 如果距离小于阈值，则归为同一组
            if distance <= vertical_threshold:
                current_group.append(curr_box)
            else:
                # 否则开始新组
                groups.append(current_group)
                current_group = [curr_box]

        groups.append(current_group)

    return groups


def detect_overlapping_text_lines(ocr_results, overlap_threshold=0.5):
    """
    检测重叠的文本行，可用于处理文本跨行过高的问题

    Args:
        ocr_results: OCR识别结果
        overlap_threshold: 重叠比例阈值

    Returns:
        list: 重叠的文本行对
    """
    overlapping_pairs = []

    if not ocr_results:
        return overlapping_pairs

    # 收集所有文本框及其中心点
    text_boxes = []
    for result in ocr_results:
        if result and len(result) > 0:
            for line in result:
                if line and len(line) > 0:
                    box = line[0]
                    # 计算包围盒
                    x_coords = [point[0] for point in box]
                    y_coords = [point[1] for point in box]
                    x0, x1 = min(x_coords), max(x_coords)
                    y0, y1 = min(y_coords), max(y_coords)

                    text_boxes.append({
                        'text': line[1][0],
                        'box': (x0, y0, x1, y1),
                        'center': calculate_rectangle_center((x0, y0, x1, y1)),
                        'height': y1 - y0
                    })

    # 检查每对文本框是否存在重叠
    for i in range(len(text_boxes)):
        for j in range(i + 1, len(text_boxes)):
            box1 = text_boxes[i]
            box2 = text_boxes[j]

            # 计算垂直方向重叠程度
            top1, bottom1 = box1['box'][1], box1['box'][3]
            top2, bottom2 = box2['box'][1], box2['box'][3]

            # 计算重叠区域
            overlap_top = max(top1, top2)
            overlap_bottom = min(bottom1, bottom2)
            overlap_height = max(0, overlap_bottom - overlap_top)

            # 计算最小高度作为参考
            min_height = min(box1['height'], box2['height'])

            # 如果重叠比例超过阈值，则认为是重叠的
            if min_height > 0 and (overlap_height / min_height) > overlap_threshold:
                overlapping_pairs.append((box1, box2))

    return overlapping_pairs
